#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
#
# This file is part of Coruja-scripts
#
# Coruja-scripts is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# Coruja-scripts is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Coruja-scripts.  If not, see <http://www.gnu.org/licenses/>.
#
# Copyright 2011 Grupo Falabrasil - http://www.laps.ufpa.br/falabrasil
#
# Author 2011: Pedro Batista - pedosb@gmail.com

import argparse
import math
import random
import numpy as np
from config import *

class VectorException(Exception):
	pass

class Vector():
	def __init__(self, data):
		self.data = data

	def dimension(self):
		return len(self.data)

	def d(self, other):
		if self.dimension() != other.dimension():
			raise VectorException("Dimensions must agree to compute distance")
		s = 0
		for i in xrange(self.dimension()):
			s += (self.data[i] - other.data[i]) ** 2
		return math.sqrt(s)

	def __iter__(self):
		return iter(self.data)

	def __str__(self):
		return str(self.data)

	def __getitem__(self, i):
		return self.data[i]

class CodebookException(Exception):
	pass

def centroid2(vector_list):
	if vector_list is None:
		return None
	c_m = vector_list[0].dimension() * [None]
	for i in xrange(len(c_m)):
		c_m[i] = float(sum([v[i] for v in vector_list]))
	c_m = np.array(c_m)
	return Vector(c_m / len(vector_list))

def centroid(vector_list):
	l = vector_list
	s = len(l)
	d = np.zeros(2 * [s])
	for i in xrange(s):
		for j in xrange(i+1, s):
			d[i, j] = l[i].d(l[j])
	d += d.transpose()
	c_sum = d.sum(0)
	return l[c_sum.argmin()]

class Codebook():
	def __init__(self,
			size=None,
			v=None,
			code_word_list=None,
			min_value=None,
			max_value=None,
			dimension=None):
		self.size = size
		self.dimension = dimension
		if size is not None:
			self.code_word_list = size * [None]
			if max_value is None:
				max_value = 2 ** 16
			if min_value is None:
				min_value = -(max_value - 1)
			if v is not None:
				j = range(len(v))
			for i in xrange(len(self.code_word_list)):
				if v is None and self.dimension is not None:
					self.code_word_list[i] = Vector([random.uniform(min_value, max_value) for j in xrange(dimension)])
				elif v is not None:
					n = random.choice(j)
					del j[j.index(n)]
					self.code_word_list[i] = v[n]
		elif code_word_list is not None:
			self.size = len(code_word_list)
			self.code_word_list = code_word_list
		else:
			self.code_word_list = None

	def best_match(self, v):
		"""
		return codebook index of the best match, the minimal distance
		"""
#		if v.dimension() != self.dimension:
#			raise CodebookException("Vector dimension must agree with codebook dimension")
		d_list = []
		for code_word in self.code_word_list:
			d_list.append(code_word.d(v))
		min_d = min(d_list)
		return d_list.index(min_d), min_d

	def classify(self, v):
		return self.best_match(v)[0]

	def __str__(self):
		return str(self.code_word_list)

def k_means(train_vector_list,
		e=config.getfloat('templates', 'k_means_error')):
	cb = Codebook(config.getint('templates', 'n'), train_vector_list)
	D_ = 0
	D = np.inf
	while True:
		#group_list = [[]] * cb.size
		group_list = {}
		for i in xrange(len(train_vector_list)):
			m, d = cb.best_match(train_vector_list[i])
			try:
				group_list[m].append(i)
			except KeyError:
				group_list[m] = []
				group_list[m].append(i)

		centroids = [None] * cb.size
		for i in xrange(len(centroids)):
			centroids[i] = centroid([train_vector_list[j] for j in group_list[i]])
		cb.code_word_list = centroids

		D = 0
		for i in xrange(len(train_vector_list)):
			D += cb.best_match(train_vector_list[i])[1]

		if abs(D - D_) > e or D - D_ != 0:
			break
		D_ = D
	return cb

if __name__=='__main__':
	#   print Vector([1,2]).dimension()
	v = k_means([Vector([1, 2]), Vector([2, 4]), Vector([2,3])], 0)
	print v.code_word_list[0], v.code_word_list[1]
#   parser = argparse.ArgumentParser(description=''=)
#   parser.add_argument('-', '--',
#	 help='')
#   args = parser.parse_args()
#   pass
#
